﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Gunningine
{
    using OpenTK;
    using OpenTK.Input;

    /// <summary>
    /// Represents a single state that the game is in at any
    /// one point in time.
    /// </summary>
    public abstract class GameState
    {
        private string name;

        private Logger logger;

        protected ResourceManager resourceMgr;

        protected RenderSystem renderSystem;

        protected InputManager inputMgr;

        protected ScriptManager scriptMgr;

        /// <summary>
        /// Is this state currently suspended?
        /// </summary>
        private bool suspended;

        /// <summary>
        /// The time this instance of the state has been running for (while
        /// not suspended).
        /// </summary>
        private float totalTime;

        public float TotalTime 
        { 
            get { return this.totalTime; } 
        }

        /// <summary>
        /// Timer for FPS calculation.
        /// </summary>
        private float fpsTimer;

        /// <summary>
        /// Current number of frames this second.
        /// </summary>
        private int frameCount;

        /// <summary>
        /// Current FPS
        /// </summary>
        public float FPS { get; private set; }

        protected List<float> fpsHistory;

        private List<string> debugText = new List<string>();

        /// <summary>
        /// Show debugging text?
        /// </summary>
        private bool showDebugText;

        /// <summary>
        /// Scripting interfaces.
        /// </summary>
        protected GameStateScriptingCore Core { get; private set; }

        protected GameStateScriptingGraphics Gfx { get; private set; }

        protected GameStateScriptingInput Input { get; private set; }

        /// <summary>
        /// Default constructor.
        /// </summary>
        public GameState()
        {
            this.name = "Unknown state";
            this.suspended = false;
            this.showDebugText = false;
        }

        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="name">state name.</param>
        public GameState(string name)
            : this()
        {
            this.name = name;
            this.suspended = false;
            this.showDebugText = false;
        }

        /// <summary>
        /// Initialise manager for the state to use.
        /// </summary>
        /// <param name="resourceMgr"></param>
        /// <param name="gameStateMgr"></param>
        /// <param name="renderSystem"></param>
        /// <param name="inputMgr"></param>
        /// <param name="scriptMgr"></param>
        /// <param name="bulletMachine"></param>
        public void InitialiseManagers(ResourceManager resourceMgr,
                                       GameStateManager gameStateMgr,
                                       RenderSystem renderSystem,
                                       InputManager inputMgr,
                                       ScriptManager scriptMgr)
        {
            this.resourceMgr = resourceMgr;
            this.renderSystem = renderSystem;
            this.inputMgr = inputMgr;
            this.scriptMgr = scriptMgr;

            this.Core = new GameStateScriptingCore();
            this.Gfx = new GameStateScriptingGraphics(this.renderSystem);
            this.Input = new GameStateScriptingInput(this.inputMgr, this.renderSystem);
        }

        /// <summary>
        /// Add a line to debug text.
        /// </summary>
        /// <param name="text"></param>
        protected void AddDebugText(string text)
        {
            this.debugText.Add(text);
        }

        /// <summary>
        /// Is debug text being rendered?
        /// </summary>
        /// <returns></returns>
        protected bool IsDebugTextVisible()
        {
            return this.showDebugText;
        }

        /// <summary>
        /// Toggle debug text display.
        /// </summary>
        /// <param name="show"></param>
        protected void ShowDebugText(bool show)
        {
            this.showDebugText = show;
        }

        /// <summary>
        /// Toggle displaying debug text.
        /// </summary>
        protected void ToggleDebugText()
        {
            ShowDebugText(!IsDebugTextVisible());
        }

        /// <summary>
        /// Change state.
        /// </summary>
        /// <param name="nextState"></param>
        public void ChangeState(GameState nextState)
        {
            throw new ChangeStateException(nextState);
        }

        /// <summary>
        /// Exit current state.
        /// </summary>
        public void ExitState()
        {
            throw new ChangeStateException();
        }

        /// <summary>
        /// Called when this state is entered (for the first time).
        /// </summary>
        /// <param name="prevState">the previous game state (which we are exiting from).</param>
        public void Enter(GameState prevState)
        {
            this.totalTime = 0.0f;
            this.fpsTimer = 0.0f;
            this.frameCount = 0;
            this.FPS = 0.0f;
            this.fpsHistory = new List<float>();

            EnterPre();
            EnterImpl(prevState);
            EnterPost();
        }

        /// <summary>
        /// User-defined implementation for Enter.
        /// </summary>
        /// <param name="prevState">the previous game state (which we are exiting from).</param>
        protected virtual void EnterImpl(GameState prevState)
        {
        }

        /// <summary>
        /// Called before EnterImpl().
        /// </summary>
        protected virtual void EnterPre()
        {
        }

        /// <summary>
        /// Called after EnterImpl().
        /// </summary>
        protected virtual void EnterPost()
        {
        }

        /// <summary>
        /// Called when this state is finished and we move to a new one.
        /// </summary>
        public void Exit()
        {
            ExitPre();
            ExitImpl();
            ExitPost();
        }

        /// <summary>
        /// User-defined implementation for Exit.
        /// </summary>
        protected virtual void ExitImpl()
        {
        }

        /// <summary>
        /// Called before ExitImpl().
        /// </summary>
        protected virtual void ExitPre()
        {
        }
        
        /// <summary>
        /// Called after ExitImpl().
        /// </summary>
        protected virtual void ExitPost()
        {
        }

        /// <summary>
        /// Called when we want to temporarily move to another state before returning to this one.
        /// </summary>
        /// <param name="newState">the state we are about to move to.</param>
        public void Suspend(GameState newState)
        {
            if (this.suspended)
            {
                throw new Exception("Tried to suspend state '" + name + "' but it was already suspended.");
            }

            suspended = true;

            // User-supplied implementation.
            SuspendPre();
            SuspendImpl(newState);
            SuspendPost();
        }

        /// <summary>
        /// User-defined implementation for suspend.
        /// </summary>
        /// <param name="newState">the state we are about to move to.</param>
        protected virtual void SuspendImpl(GameState newState)
        {
        }

        /// <summary>
        /// Called before SuspendImpl()
        /// </summary>
        protected virtual void SuspendPre()
        {
        }

        /// <summary>
        /// Called after SuspendImpl()
        /// </summary>
        protected virtual void SuspendPost()
        {
        }

        /// <summary>
        /// Called after we have suspended this state and wish to return to it.
        /// </summary>
        /// <param name="oldState">the state we are moving from.</param>
        public void Resume(GameState oldState)
        {
            if (!this.suspended)
            {
                throw new Exception("Tried to resume state '" + name + "' but it was not suspended.");
            }

            suspended = false;

            // Reset FPS calculation.
            this.totalTime = 0.0f;
            this.fpsTimer = 0.0f;
            this.frameCount = 0;

            // User-supplied implementation.
            ResumePre();
            ResumeImpl(oldState);
            ResumePost();
        }

        /// <summary>
        /// User-defined implementation for resume.
        /// </summary>
        /// <param name="newState">the state we are moving from.</param>
        protected virtual void ResumeImpl(GameState newState)
        {
        }

        /// <summary>
        /// Called before ResumeImpl()
        /// </summary>
        protected virtual void ResumePre()
        {
        }

        /// <summary>
        /// Called after ResumeImpl()
        /// </summary>
        protected virtual void ResumePost()
        {
        }

        /// <summary>
        /// Update this state.
        /// </summary>
        /// <param name="frameTime">time since last update, in seconds.</param>
        /// <param name="inputMgr">input manager to query for input.</param>
        public void Update(float frameTime, InputManager inputMgr)
        {
            // Update FPS counter.
            this.frameCount++;
            this.totalTime += frameTime;
            this.fpsTimer += frameTime;

            if (this.fpsTimer > 1.0f)
            {
                this.FPS = this.frameCount;

                this.fpsTimer -= 1.0f;
                this.frameCount = 0;

                this.fpsHistory.Add(this.FPS);
            }

            UpdatePre();
            UpdateImpl(frameTime, inputMgr);
            UpdatePost();
        }

        /// <summary>
        /// User-defined implementation for resume.
        /// </summary>
        /// <param name="frameTime">time since last update, in seconds.</param>
        /// <param name="inputMgr">input manager to query for input.</param>
        protected virtual void UpdateImpl(float frameTime, InputManager inputMgr)
        {
        }

        /// <summary>
        /// Called before UpdateImpl()
        /// </summary>
        protected virtual void UpdatePre()
        {
        }

        /// <summary>
        /// Called after ResumeImpl()
        /// </summary>
        protected virtual void UpdatePost()
        {
        }

        /// <summary>
        /// Render this state.
        /// </summary>
        /// <param name="renderSystem">render system to use for rendering.</param>
        public void Render(RenderSystem renderSystem)
        {
            // Just call user function for now.
            RenderPre(renderSystem);
            RenderImpl(renderSystem);
            RenderPost(renderSystem);

            // Render debug information if desired.
            if (this.showDebugText)
            {
                renderSystem.Set2dProjection();

                renderSystem.SetColour(1, 1, 1, 1);
                string fpsText = String.Format("FPS: {0}", this.FPS);
                renderSystem.Print(0, 0, "Fonts/ArialBlack-Small", fpsText);

                for (int i = 0; i < this.debugText.Count; ++i)
                {
                    renderSystem.Print(0, (i + 1) * 20, "Fonts/ArialBlack-Small", debugText[i]);
                }
            }

            // Clear debug text
            this.debugText.Clear();
        }

        /// <summary>
        /// User-defined implementation for render.
        /// </summary>
        /// <param name="renderSystem">render system to use for rendering.</param>
        protected virtual void RenderImpl(RenderSystem renderSystem)
        {
        }

        /// <summary>
        /// Called before RenderImpl()
        /// </summary>
        protected virtual void RenderPre(RenderSystem renderSystem)
        {
        }

        /// <summary>
        /// Called after ResumeImpl()
        /// </summary>
        protected virtual void RenderPost(RenderSystem renderSystem)
        {
        }

    }
}